'use strict';

/**
 * 用户逻辑控制器
 */
const Code = require('../common/error_code');
const Cons = require('../common/constants');
const mongoose = require('mongoose');
const util = require('util');
const globalConfig = require('../../config/global_config');
const userModel = require('../models/user.model');
const newFaceFeatureModel = require('../models/new_face_feature.model');
const faceFeatureModel = require('../models/face_feature.model');
const configModel = require('../models/config.model');
const guardModel = require('../models/guard.model');
const downloadModel = require('../models/download_log.model')
const batchLogModel = require('../models/bactchLog.model')
const Config = require('../controllers/config');
const Util = require('../utils/hobot_util');
const moment = require('moment');
const fs = require('fs');
const XLSX = require('xlsx');
// const ImageUtil = require('../utils/image');
const featureModel = require('../../config/global_config').feature.model;
const featureUrl = require('../../config/global_config').feature;
const resConfig = require('../../config/global_config').res;
const unzip = require('../utils/unzip');
const path = require('path');
const logger = require('../utils/logger');
const httpUtil = require('../utils/http');
const fileUtils = require('../utils/file_util');
const Base64 = require('../utils/base64');
const UserRecordModel = require('../models/record_user.model');

const ipcModel = require('../models/ipc.model');

/**
 * 员工批量注册
 */
async function register_batch(ctx) {
    const file = ctx.req.file.buffer;
    const workBook = XLSX.read(file);
    const workSheet = workBook.Sheets['Sheet1'];
    // const a1 = workSheet['A1'].v;
    // const b1 = workSheet['B1'].v;
    // const c1 = workSheet['C1'].v;
    // const d1 = workSheet['D1'].v;
    // const e1 = workSheet['E1'].v;
    // const f1 = workSheet['F1'].v;
    // const g1 = workSheet['G1'].v;
    // if (a1 != '账号' || b1 != '工号' || c1 != '姓名' || d1 != '性别' || e1 != '邮箱'
    //     || f1 != '部门' || g1 != '电话（可选）') {
    //     return ctx.error({
    //         code: Code.RET_USER_REGISTER_BATCH_ERR_PARAM.code,
    //         msg: util.format(Code.RET_USER_REGISTER_BATCH_ERR_PARAM.msg, '导入文件格式')
    //     });
    // }

    const tableData = XLSX.utils.sheet_to_json(workSheet)
    const tableKeyMap = {
        '账号': '_id',
        '工号': 'u_no',
        '姓名': 'u_nm',
        '性别': 'u_sx',
        '邮箱（可选）': 'u_em',
        '部门': 'u_dm',
        '电话（可选）': 'u_pn'
    };
    const sexMap = {
        '男': 1,
        '女': 0
    };
    let depts = await Config.getDeptList();
    const tableDataStandard = [];
    let tableIds = {}
    for (let i = 0; i < tableData.length; i++) {
        const item = tableData[i];
        const dataTemp = {}
        for (let column in item) {
            const v = item[column].trim();
            if (column == '性别' && v != '男' && v != '女') {
                return ctx.error({
                    code: Code.RET_USER_REGISTER_BATCH_ERR_PARAM.code,
                    msg: util.format(Code.RET_USER_REGISTER_BATCH_ERR_PARAM.msg,
                        util.format('第%s行 的性别格式', i + 1))
                });
            }
            if (column == '部门' && Config.getConfigKey(depts, v) === '') {
                // return ctx.error({
                //     code: Code.RET_USER_REGISTER_BATCH_ERR_PARAM.code,
                //     msg: util.format(Code.RET_USER_REGISTER_BATCH_ERR_PARAM.msg,
                //         util.format('第%s行 的部门格式', i + 1))
                // });
                let ret = await Config.dept_internal({
                    action: Cons.CONFIG_DEPT_ACTION_ADD,
                    label: v
                });
                if (ret < 0) {
                    return ctx.error({
                        code: Code.RET_USER_REGISTER_BATCH_ERR_PARAM.code,
                        msg: util.format(Code.RET_USER_REGISTER_BATCH_ERR_PARAM.msg,
                            util.format('第%s行 的性别格式', i + 1))
                    });
                }
                depts = await Config.getDeptList();
            }
            if (column == '账号' && v) {
                const user = await userModel.findOne({ _id: v });
                if (user) {
                    return ctx.error({
                        code: Code.RET_USER_REGISTER_BATCH_ERR_PARAM.code,
                        msg: util.format(Code.RET_USER_REGISTER_BATCH_ERR_PARAM.msg,
                            util.format('第%s行 的账号已经存在', i + 1))
                    });
                }
            }
            if (column == '工号' && v) {
                const user = await userModel.findOne({ u_no: v });
                if (user) {
                    return ctx.error({
                        code: Code.RET_USER_REGISTER_BATCH_ERR_PARAM.code,
                        msg: util.format(Code.RET_USER_REGISTER_BATCH_ERR_PARAM.msg,
                            util.format('第%s行 的工号已经存在', i + 1))
                    });
                }
            }
            //2018年03月07日15:19:23 gk add 不清楚为什么要  && v

            if (column == '邮箱（可选）' && v) {
                if (!Util.isEmail(v)) {
                    return ctx.error({
                        code: Code.RET_USER_REGISTER_BATCH_ERR_PARAM.code,
                        msg: util.format(Code.RET_USER_REGISTER_BATCH_ERR_PARAM.msg,
                            util.format('第%s行 的邮箱格式', i + 1))
                    });
                }
            }
            dataTemp[tableKeyMap[column]] = v;
        }
        //校验是否缺少必选项
        for (var key of Object.keys(dataTemp)) {
            if (!dataTemp[key].trim()) {
                delete dataTemp[key]
            }
        }
        let column_name = '';
        if (!dataTemp._id) {
            column_name = '账号';
            // } else if (!dataTemp.u_no) {
            //     column_name = '工号';
        } else if (!dataTemp.u_nm) {
            column_name = '姓名';
        } else if (!dataTemp.u_sx) {
            column_name = '性别';
        } else if (!dataTemp.u_dm) {
            column_name = '部门';
        }
        if (column_name) {
            return ctx.error({
                code: Code.RET_USER_REGISTER_BATCH_ERR_PARAM.code,
                msg: util.format(Code.RET_USER_REGISTER_BATCH_ERR_PARAM.msg,
                    util.format('第%s行 %s 是空值', i + 1, column_name))
            });
        }
        dataTemp['u_sx'] = sexMap[dataTemp['u_sx']];
        dataTemp['u_dm'] = Config.getConfigKey(depts, dataTemp['u_dm']);

        dataTemp.u_tp = Cons.USER_TYPE_STAFF;
        dataTemp.u_st = Cons.USER_STATUS_OK;
        if (tableIds[dataTemp._id] != undefined) {
            return ctx.error({
                code: Code.RET_USER_REGISTER_BATCH_ERR_PARAM.code,
                msg: util.format(Code.RET_USER_REGISTER_BATCH_ERR_PARAM.msg,
                    util.format('第%s行  id: %s 重复', i + 1, dataTemp._id))
            });
        }
        tableIds[dataTemp._id] = 1

        tableDataStandard.push(dataTemp)
    }

    // 转换完成的数据，已经通过校验，直接插入数据库
    tableDataStandard.forEach(async function (item) {
        await userModel.create(item);
    });
    ctx.success(Code.RET_USER_REGISTER_BATCH_OK);
}

/**
 * 访客批量注册
 * @param ctx
 * @returns {Promise.<void>}
 */
async function guest_register_batch(ctx) {
    const params = ctx.req.body
    const file = ctx.req.file.buffer;
    const workBook = XLSX.read(file);
    const workSheet = workBook.Sheets['Sheet1'];

    const tableData = XLSX.utils.sheet_to_json(workSheet)

    const tableKeyMap = {
        '姓名（必填）': 'u_nm',
        '性别': 'u_sx',
        '接待人': 'g_re',
        '单位': 'g_cp',
        '欢迎语': 'g_mk',
        '电话': 'u_pn',
        '来访日期（必填）': 'p_fr',
        '结束日期（必填）': 'p_to',
        '员工类型（必填）': 'u_tp',
    };
    // 必填字段组
    const tableKeysRequired = ['姓名（必填）', '来访日期（必填）', '结束日期（必填）', '员工类型（必填）']
    const sexMap = {
        '男': 1,
        '女': 0
    };
    const typeMap = {
        'VIP': Cons.USER_TYPE_VIP,
        '临时访客': Cons.USER_TYPE_GUEST
    }
    const tableDataStandard = [];
    for (let i = 0; i < tableData.length; i++) {
        const item = tableData[i];
        const dataTemp = {}
        let keys = Object.keys(item)
        for (let key of tableKeysRequired) {
            if (keys.indexOf(key) < 0) {
                return ctx.error({
                    code: Code.RET_USER_REGISTER_BATCH_ERR_PARAM.code,
                    msg: util.format(Code.RET_USER_REGISTER_BATCH_ERR_PARAM.msg,
                        util.format('第%s行 %s 是空值', i + 1, key))
                });
            }
        }
        for (let column in item) {
            dataTemp[tableKeyMap[column]] = item[column].trim();
        }

        // 备注
        if (dataTemp['g_mk']) {
            dataTemp['g_mk'] = Base64.encode(dataTemp['g_mk']);
        }

        //性别
        if (dataTemp['u_sx']) {
            if (Object.keys(sexMap).indexOf(dataTemp['u_sx']) < 0) {
                return ctx.error({
                    code: Code.RET_USER_REGISTER_BATCH_ERR_PARAM.code,
                    msg: util.format(Code.RET_USER_REGISTER_BATCH_ERR_PARAM.msg,
                        util.format('第%s行 的性别格式存在问题', i + 1))
                });
            }
            dataTemp['u_sx'] = sexMap[dataTemp['u_sx']]
        }

        //type
        if (Object.keys(typeMap).indexOf(dataTemp['u_tp']) < 0) {
            return ctx.error({
                code: Code.RET_USER_REGISTER_BATCH_ERR_PARAM.code,
                msg: util.format(Code.RET_USER_REGISTER_BATCH_ERR_PARAM.msg,
                    util.format('第%s行 的员工类型格式存在问题', i + 1))
            });
        }
        dataTemp['u_tp'] = typeMap[dataTemp['u_tp']]

        const dataStr = 'YYYY-MM-DD'
        //访问日期
        dataTemp['p_fr'] = moment(dataTemp['p_fr'], dataStr)
        if (!dataTemp['p_fr'].isValid()) {
            return ctx.error({
                code: Code.RET_USER_REGISTER_BATCH_ERR_PARAM.code,
                msg: util.format(Code.RET_USER_REGISTER_BATCH_ERR_PARAM.msg,
                    util.format('第%s行 的访问日期格式存在问题', i + 1))
            });
        }

        //结束日期
        dataTemp['p_to'] = moment(dataTemp['p_to'], dataStr)
        if (!dataTemp['p_to'].isValid) {
            return ctx.error({
                code: Code.RET_USER_REGISTER_BATCH_ERR_PARAM.code,
                msg: util.format(Code.RET_USER_REGISTER_BATCH_ERR_PARAM.msg,
                    util.format('第%s行 的结束日期格式存在问题', i + 1))
            });
        }

        //验证时间区间
        if (dataTemp['p_to'].isBefore(dataTemp['p_fr'])) {
            return ctx.error({
                code: Code.RET_USER_REGISTER_BATCH_ERR_PARAM.code,
                msg: util.format(Code.RET_USER_REGISTER_BATCH_ERR_PARAM.msg,
                    util.format('第%s行 结束日期不得早于来访日期', i + 1))
            });
        }

        dataTemp.u_st = Cons.USER_STATUS_OK;
        if (params.adminKey && params.adminKey == 'admin123') {
            dataTemp['_id'] = await userModel.findOne({ u_nm: dataTemp['u_nm'] }, { _id: 1 })
            if (!dataTemp['_id']) {
                dataTemp['_id'] = new mongoose.Types.ObjectId()
                await userModel.create(dataTemp);
            } else {
                await userModel.update({ _id: dataTemp['_id'] }, dataTemp)
            }
            continue
        } else {
            dataTemp['_id'] = new mongoose.Types.ObjectId();
        }
        tableDataStandard.push(dataTemp)
    }
    // 转换完成的数据，已经通过校验，直接插入数据库
    if (!params.adminKey || params.adminKey != 'admin123') {
        for (let item of tableDataStandard) {
            await userModel.create(item);
        }
    }
    ctx.success(Code.RET_USER_REGISTER_BATCH_OK);
}

/**
 * 特征批量注册
 * 1.接受压缩包
 * 2.解压
 * 3.循环上传
 *  3.1 成功
 *  3.2 失败
 * 4.保存日志
 *
 * 参数 压缩包，
 * 类型 员工 或者访客
 */
async function feature_register_batch(ctx) {
    //filename 文件名称，path上传后文件的临时路径，mimetype文件类型
    const { originalname, filename, mimetype } = ctx.req.file
    const dir = path.join(resConfig.uploader_dir, filename.split(".")[0])
    const params = ctx.req.body
    let user_type = params.user_type
    let file_type = params.file_type
    if (!filename || !user_type
        || [Cons.FEATURE_TYPE_REGISTER, Cons.FEATURE_TYPE_STRANGER].indexOf(user_type) < 0
        || !file_type
        || [Cons.ZIPFILE_TYPE_FEATURE, Cons.ZIPFILE_TYPE_AVATAR].indexOf(file_type) < 0) {
        ctx.error(Code.RET_FEATURE_UPLOAD_ERR_PARAM);
        return
    }
    //返回结果，接下来处理文件
    // ctx.success(Code.RET_FEATURE_UPLOAD_OK);

    //TODO 插入数据库，返回文件路径
    // dataTemp['_id'] = new mongoose.Types.ObjectId();
    let dbId = await downloadModel.create({})
    dbId = dbId._id
    // 0.判断文件目录是否存在
    let logDir = resConfig.downloadlog_dir
    fileUtils.mkdirsSync(logDir)
    // 1.获取文件名称,转换为log路径
    let logName = dbId + '.txt'
    let logPath = path.join(logDir, logName)

    //更新数据库
    await downloadModel.update({ _id: dbId }, {
        file_name: originalname,
        download_time: new Date(),
        log_path: logPath,
        user_type: user_type,
        file_type: file_type
    })

    let result = await new Promise(async (resolve, reject) => {
        const unzipExtractor = unzip.Extract({ path: dir });
        await fs.createReadStream(path.join(resConfig.uploader_dir, filename)).pipe(unzipExtractor)
        //这边担心特征照片过多，所以先解压本地，不缓存内存
        unzipExtractor.on('close', async () => {
            //更新状态，总条数，正常，出错
            let info = {
                total: 0,
                current: 0,
                correctNum: 0,
                errorNum: 0,
                msgs: [],
                msg: '',
                addError(msg) {
                    this.errorNum++
                    this.msgs.push(msg)
                },
                addCorrect() {
                    this.correctNum++
                },
                addOneWithError(msg) {
                    this.current++
                    this.addError(msg)
                },
                addOneWithOutError() {
                    this.current++
                    this.addCorrect()
                },
                checkSum(dbId) {
                    if (this.current != total) {
                        downloadModel.update({ _id: dbId }, {
                            download_statue: Cons.DOWNLOAD_INFO_ERROR
                        })
                    }
                },
                writeTofile(filePath) {
                    let msg = ''
                    msg += '==============基本信息====================\r\n'
                    msg += util.format('共处理数据：\t%s组\r\n', this.total)
                    msg += util.format('上传成功数据：\t%s组\r\n', this.correctNum)
                    msg += util.format('上传失败数据：\t%s组\r\n', this.errorNum)
                    msg += '==============详细信息====================\r\n'
                    for (let index in this.msgs) {
                        msg += util.format('%s\t==>\t%s\r\n', Number(index) + 1, this.msgs[index])
                    }
                    this.msg = msg;
                    fs.writeFileSync(filePath, msg)
                }
            }
            try {
                let parent = fs.readdirSync(dir)
                info.total = parent.length
                if (file_type == Cons.ZIPFILE_TYPE_FEATURE) {
                    for (let index in parent) {
                        //更新数据库
                        await downloadModel.update({ _id: dbId }, {
                            download_total: info.total,
                            download_current: Number(index) + 1,
                            download_statue: Cons.DOWNLOAD_INFO_UPLOADING
                        })
                        let item = parent[index]
                        let statue = fs.statSync(path.join(dir, item))
                        if (!statue.isDirectory) {
                            info.addOneWithError(util.format('%s 不是文件夹类型，格式错误', item))
                            continue
                        }

                        let regId = await judgeRegId(user_type, item, info)
                        if (!regId) {
                            continue
                        }
                        //特征注册
                        let imgDatas = []
                        let imgNames = []
                        try {
                            for (let img of fs.readdirSync(path.join(dir, item))) {
                                imgNames.push(img)
                                imgDatas.push({
                                    // picdata: (await ImageUtil.resize(path.join(dir, item, img), 1200)).toString('base64'),
                                    picdata: fs.readFileSync(path.join(dir, item, img)).toString('base64'),
                                    type: 'jpg',
                                    width: '100',
                                    height: '100'
                                })
                                // imgDatas.push(fs.readFileSync(path.join(dir, item, img)).toString('base64'))
                            }
                        } catch (error) {
                            info.addOneWithError(util.format("%s 文件读取错误，注册失败", item))
                            continue;
                        }
                        // 删除注册
                        const url = featureUrl.register;
                        // 删除特征
                        const deleteUrl = featureUrl.delete;

                        let result = ""
                        try {
                            await httpUtil.post(deleteUrl, {
                                _id: [regId + ""]
                            })
                            result = await httpUtil.post(url, {
                                _id: regId + "",
                                pictures: imgDatas
                            })
                            if (!result) {
                                info.addOneWithError(util.format("%s 服务器错误，注册失败", item))
                                continue
                            }
                        } catch (err) {
                            info.addOneWithError(util.format("%s 服务器错误，注册失败", item))
                            continue
                        }

                        let resultMaps = [
                            '图像格式有问题', '人脸小于100*100或找不到人脸', '图像中人脸多于一张', '未知错误'
                        ]

                        if (result.is_success != 0) {
                            info.addOneWithError(util.format('%s照片存在问题，注册失败', item))
                        } else {
                            let flag = ""

                            result.pic_results.forEach((res, index) => {
                                if (res != 0) {
                                    flag += util.format('%s:%s,', imgNames[index], resultMaps[-Number(res) - 1])
                                }
                            })
                            if (flag) {
                                info.addOneWithError(util.format('%s一部分照片存在问题，详细信息：【%s】', item, flag))
                            } else {
                                info.addOneWithOutError()
                            }
                        }
                    }
                } else {//头像
                    for (let index in parent) {
                        //更新数据库
                        await downloadModel.update({ _id: dbId }, {
                            download_total: info.total,
                            download_current: Number(index) + 1,
                            download_statue: Cons.DOWNLOAD_INFO_UPLOADING
                        })
                        let item = parent[index]
                        let statue = fs.statSync(path.join(dir, item))
                        if (!statue.isFile()) {
                            info.addOneWithError(util.format('%s 不是文件类型，格式错误', item))
                            continue
                        }
                        let regId = path.basename(item, path.extname(item))
                        regId = await judgeRegId(user_type, regId, info)
                        if (!regId) {
                            continue
                        }
                        //头像注册
                        let dstfile = regId + '.jpeg';
                        fs.copyFileSync(path.join(dir, item), path.join(resConfig.avatar_dir, dstfile))
                        await userModel.update({ _id: regId }, {
                            u_hp: `${dstfile}?${new Date().getTime()}`
                        })
                        info.addOneWithOutError()
                    }
                }
                await downloadModel.update({ _id: dbId }, {
                    download_statue: Cons.DOWNLOAD_INFO_COMPLETED
                })
            } catch (err) {
                info.addOneWithError(util.format("上传出错，当前进度 （%s/%s），详细信息：\r\n", info.current, info.total, err))
            }
            info.writeTofile(logPath)
            // ctx.success({
            //     code: Code.RET_FEATURE_UPLOAD_OK.code,
            //     msg: info.msg
            // });
            resolve(info.msg);
        })

        unzipExtractor.on('error', async (err) => {
            await downloadModel.update({ _id: dbId }, {
                download_statue: Cons.DOWNLOAD_INFO_UNZIPERROR
            })
            throw err
        })
    });
    ctx.success({
        code: Code.RET_FEATURE_UPLOAD_OK.code,
        msg: result
    });
}

/**
 * 判断ID 是否可以注册
 * @param user_type
 * @param item
 * @param info
 * @returns {Promise.<boolean>}
 */
async function judgeRegId(user_type, item, info) {
    let result = false
    if (user_type == Cons.FEATURE_TYPE_STRANGER) {
        //判断姓名是否唯一
        let rst = await userModel.find({ u_nm: item })
        if (!rst || rst.length == 0) {
            info.addOneWithError(util.format('用户 %s 名称不存在，注册失败', item))
            result = false
        } else if (rst.length > 1) {
            info.addOneWithError(util.format('数据库中存在多个 %s,请手动注册', item))
            result = false
        } else {
            result = rst[0]._id
        }
    } else {
        //判断id是否存在
        let rst = await userModel.findById(item)
        if (!rst) {
            info.addOneWithError(util.format('用户 %s 名称不存在，注册失败', item))
            result = false
        } else {
            result = rst._id
        }
    }
    return result
}

/**
 * 判断学员学号 是否可以注册
 * @param user_type
 * @param item
 * @param info
 * @returns {Promise.<boolean>}
 */
async function judgeStuentNum(name, info) {
    let result = false
    //判断姓名是否唯一
    let rst = await userModel.find({ u_nm: name, u_tp:Cons.USER_TYPE_STUDENT })
    if (!rst || rst.length == 0) {
        info.addOneWithError(util.format('学员 %s 不存在，注册失败', name))
        result = false
    } else if (rst.length > 1) {
        info.addOneWithError(util.format('数据库中存在多个 %s,请手动注册', name))
        result = false
    } else {
        result = rst[0]
    }
    return result
}

/**
 * 新增用户
 */
async function register(ctx) {
    const params = ctx.request.body;
    // 校验参数
    if (!params.user_name || !params.user_type) {
        return ctx.error(Code.RET_USER_REGISTER_ERR_PARAM);
    }
    if (params.user_type != Cons.USER_TYPE_STAFF && params.user_type != Cons.USER_TYPE_TRAINEE
        && params.user_type != Cons.USER_TYPE_GUEST && params.user_type != Cons.USER_TYPE_VIP) {
        return ctx.error(Code.RET_USER_REGISTER_ERR_PARAM);
    }
    // 工号不能重复
    if (!Util.isEmpty(params.user_no)) {
        const user = await userModel.findOne({ u_no: params.user_no });
        if (user) {
            return ctx.error(Code.RET_USER_REGISTER_ERR_USER_NO_EXIST);
        }
    }

    let model = {
        u_nm: params.user_name,
        u_tp: params.user_type,
        u_st: Cons.USER_STATUS_OK
    };

    model._id = new mongoose.Types.ObjectId();
    if (!Util.isEmpty(params.user_avatar)) {
        // model.u_hp = Buffer.from(params.user_avatar, 'base64');
        const newfile = model._id + '.jpeg';
        model.u_hp = saveAvatar(Buffer.from(params.user_avatar, 'base64'), '', newfile);
    }
    if (!Util.isEmpty(params.access_from)) {
        model.p_fr = moment(params.access_from);
    }
    if (!Util.isEmpty(params.access_to)) {
        model.p_to = moment(params.access_to);
    }
    if (!Util.isEmpty(params.user_sex)) {
        model.u_sx = params.user_sex;
    }
    if (!Util.isEmpty(params.user_no)) {
        model.u_no = params.user_no;
    }
    if (!Util.isEmpty(params.user_email)) {
        model.u_em = params.user_email;
        if (!Util.isEmail(model.u_em)) {
            return ctx.error(Code.RET_USER_REGISTER_ERR_PARAM)
        }
    }
    if (!Util.isEmpty(params.user_dept)) {
        model.u_dm = params.user_dept;
    }
    if (!Util.isEmpty(params.user_phone)) {
        model.u_pn = params.user_phone;
    }
    if (!Util.isEmpty(params.user_position)) {
        model.u_pt = params.user_position;
    }
    if (!Util.isEmpty(params.user_mark)) {
        model.g_mk = Base64.encode(params.user_mark);
    }
    if (!Util.isEmpty(params.user_mark2)) {
        model.g_mk2 = Base64.encode(params.user_mark2);
    }
    if (!Util.isEmpty(params.user_company)) {
        model.g_cp = params.user_company;
    }
    if (!Util.isEmpty(params.user_receptionist)) {
        model.g_re = params.user_receptionist;
    }

    const rst = await userModel.create(model);
    ctx.success(Code.RET_USER_REGISTER_OK,
        { user_id: rst._id, user_name: rst.u_nm });
}

/**
 * 更新显示头像
 */
async function update_avatar(ctx) {
    const params = ctx.req.body;
    // 校验参数
    if (!params.user_id) {
        return ctx.error(Code.RET_USER_UPDATE_AVATAR_ERR_PARAM);
    }

    // 检验id是否正确
    const cdt = {
        _id: params.user_id
    };
    const user = await userModel.findOne(cdt);
    if (!user) {
        return ctx.error(Code.RET_USER_UPDATE_AVATAR_ERR_NOT_EXIST);
    }

    // 头像文件操作
    let url = '';
    if (ctx.req.file && ctx.req.file.buffer) {
        // const origfile = Util.getFileNameFromUrl(user.u_hp);
        const origfile = user.u_hp;
        const newfile = params.user_id + Util.getFileExt(ctx.req.file.originalname);
        url = saveAvatar(ctx.req.file.buffer, origfile, newfile);
    }
    await userModel.update(cdt, { u_hp: url });
    ctx.success(Code.RET_USER_UPDATE_AVATAR_OK, { user_avatar: url });
}

/**
 * 更新用户信息
 */
async function update(ctx) {
    const params = ctx.request.body;
    // 校验参数
    if (!params.user_id) {
        return ctx.error(Code.RET_USER_UPDATE_ERR_PARAM);
    }

    // 检验id是否正确
    const cdt = {
        _id: params.user_id
    };
    const user = await userModel.findOne(cdt);
    if (!user) {
        return ctx.error(Code.RET_USER_UPDATE_ERR_PARAM);
    }

    let upd = {};
    if (!Util.isEmpty(params.user_name)) {
        upd.u_nm = params.user_name;
    }
    if (!Util.isEmpty(params.user_sex)) {
        upd.u_sx = params.user_sex;
    }
    if (!Util.isEmpty(params.user_dept)) {
        upd.u_dm = params.user_dept;
    }
    if (!Util.isEmpty(params.user_email)) {
        upd.u_em = params.user_email;
        if (!Util.isEmail(upd.u_em)) {
            return ctx.error(Code.RET_USER_UPDATE_ERR_PARAM)
        }
    }
    if (!Util.isEmpty(params.user_phone)) {
        upd.u_pn = params.user_phone;
    }
    if (!Util.isEmpty(params.user_no)) {
        upd.u_no = params.user_no;
    }
    if (!Util.isEmpty(params.user_position)) {
        upd.u_pt = params.user_position;
    }
    if (!Util.isEmpty(params.user_mark)) {
        upd.g_mk = Base64.encode(params.user_mark);
    }
    if (!Util.isEmpty(params.user_mark2)) {
        upd.g_mk2 = Base64.encode(params.user_mark2);
    }
    if (!Util.isEmpty(params.user_company)) {
        upd.g_cp = params.user_company;
    }
    if (!Util.isEmpty(params.user_receptionist)) {
        upd.g_re = params.user_receptionist;
    }
    if (!Util.isEmpty(params.access_from)) {
        upd.p_fr = moment(params.access_from);
    }
    if (!Util.isEmpty(params.access_to)) {
        upd.p_to = moment(params.access_to);
    }
    if (!Util.isEmpty(params.user_avatar)) {
        // 改成url方式了
        // upd.u_hp = Buffer.from(params.user_avatar, 'base64');
        // const origfile = Util.getFileNameFromUrl(user.u_hp);
        const origfile = user.u_hp;
        const newfile = params.user_id + '.jpeg';
        upd.u_hp = saveAvatar(Buffer.from(params.user_avatar, 'base64'), origfile, newfile);
    }

    if (!Util.isEmpty(params.user_type)) {  // 2019/04/15 新增用户类型可更改
        if (params.user_type != Cons.USER_TYPE_STAFF && params.user_type != Cons.USER_TYPE_TRAINEE
            && params.user_type != Cons.USER_TYPE_GUEST && params.user_type != Cons.USER_TYPE_VIP
            && params.user_type != Cons.USER_TYPE_STRANGER) {
            return ctx.error(Code.RET_USER_UPDATE_ERR_PARAM);
        }
        upd.u_tp = params.user_type;
        await change_record_user_type([params.user_id], params.user_type);
    }

    await userModel.update(cdt, upd);
    ctx.success(Code.RET_USER_UPDATE_OK);
}

/**
 * 获取不压缩的人脸特征数组
 * @param {*} ctx
 */
async function get_feature_uncompress(user_id) {
    const feature_pic = [];
    // 人脸特征
    const faceFeature = await faceFeatureModel.findOne({ _id: user_id });
    if (faceFeature && faceFeature.features && faceFeature.features[featureModel]
        && faceFeature.features[featureModel].length > 0) {
        for (let i = 0; i < faceFeature.features[featureModel].length; i++) {
            logger.logInfo("\n ======== get_feature_uncompress - user_id:"
                + user_id + "  " + i + "======\n");
            const item = faceFeature.features[featureModel][i];
            if (item.image.data && item.image.data.buffer) {
                feature_pic.push({
                    picdata: item.image.data.buffer.toString('base64'),
                    type: item.image.type
                });
            }
        }
    }
    return feature_pic;
}


async function addFeature(ctx) {
    let params = ctx.request.body;
    if (!params.user_id || !params.feature_pic) {
        return ctx.error(Code.RET_RECOG_REGISTER_ERR_PARAM);
    }
    let features = await get_feature_uncompress(params.user_id);
    ctx.request.body.feature_pic = params.feature_pic.concat(features);

    await regFeature(ctx);
}
/**
 * 合并用户特征,将用户 id 为 from_id 的特征合并到用户 id 为 to_id 的用户，并且删除
 * 用户 id 为 from_id 的用户
 * @param {from_id, to_id} ctx
 */
async function merge_feature(ctx) {
    const params = ctx.request.body;
    logger.logInfo("\n ======== merge_feature - params:" + params.from_id + "======\n");

    let feature_pics = [];
    const from_user = await userModel.findOne({ _id: params.from_id });
    if (!from_user) {
        return ctx.error(Code.RET_USER_MERGE_FEATURE_ERR_FROM_NOT_EXIST);
    }

    const to_user = await userModel.findOne({ _id: params.to_id });
    if (!to_user) {
        return ctx.error(Code.RET_USER_MERGE_FEATURE_ERR_TO_NOT_EXIST);
    }

    feature_pics = feature_pics.concat(await get_feature_uncompress(params.to_id));
    if (feature_pics.length > 20) {
        return ctx.error(Code.RET_USER_MERGE_FEATURE_ERR_TO_FEATURE_MAX_LIMIT);
    }
    feature_pics = feature_pics.concat(await get_feature_uncompress(params.from_id));
    if (feature_pics.length > 20) {
        return ctx.error(Code.RET_USER_MERGE_FEATURE_ERR_TO_FEATURE_MAX_LIMIT);
    }

    ctx.request.body.user_id = params.to_id;
    ctx.request.body.feature_pic = feature_pics;
    const rst = await regFeature(ctx);
    if (rst && rst.code && rst.code != 0) {
        return ctx.error(Code.RET_USER_MERGE_FEATURE_ERR_MERGE);
    }

    let res = await userModel.remove({ _id: params.from_id });
    if (res.result.n == 0) {
        return ctx.error(Code.RET_USER_MERGE_FEATURE_ERR_ERROR_FROM);
    }
    doAfterDelete([params.from_id]);

    const model = {};
    if (to_user._id != undefined) { model.u_id = to_user._id; }
    if (to_user.u_nm != undefined) { model.u_nm = to_user.u_nm; }
    if (to_user.u_sx != undefined) { model.u_sx = to_user.u_sx; }
    if (to_user.u_tp != undefined) { model.u_tp = to_user.u_tp; }
    if (to_user.u_hp != undefined) { model.u_hp = to_user.u_hp; }
    if (to_user.u_no != undefined) { model.u_no = to_user.u_no; }
    if (to_user.u_dm != undefined) { model.u_dm = to_user.u_dm; }
    if (to_user.u_pn != undefined) { model.u_pn = to_user.u_pn; }
    if (to_user.u_em != undefined) { model.u_em = to_user.u_em; }
    if (to_user.u_pt != undefined) { model.u_pt = to_user.u_pt; }
    if (to_user.g_mk != undefined) { model.g_mk = to_user.g_mk; }
    if (to_user.g_cp != undefined) { model.g_cp = to_user.g_cp; }
    if (to_user.g_re != undefined) { model.g_re = to_user.g_re; }
    if (to_user.u_st != undefined) { model.u_st = to_user.u_st; }
    if (to_user.p_fr != undefined) { model.p_fr = to_user.p_fr; }
    if (to_user.p_to != undefined) { model.p_to = to_user.p_to; }
    if (to_user.liveness != undefined) { model.liveness = to_user.liveness; }

    const upd = {
        '$set': model
    }
    res = await UserRecordModel.update({ u_id: params.from_id }, upd, { "multi": true });

    ctx.success(Code.RET_USER_MERGE_FEATURE_OK);
}

/**
 * 将用户还原回陌生人
 * @param {*} ctx
 */
async function recovery(ctx) {

    const params = ctx.request.body;
    // 校验参数
    if (!params.user_id || params.user_id.length === 0) {
        return ctx.error(Code.RET_USER_RECOVERY_ERR_PARAM);
    }

    const cdt = {
        _id: params.user_id
    }
    await userModel.update(cdt, { '$set': { "u_tp": Cons.USER_TYPE_STRANGER } }, { "multi": true });

    await change_record_user_type([params.user_id], Cons.USER_TYPE_STRANGER);
    ctx.success(Code.RET_USER_RECOVERY_OK);
}

async function del_internal(arrUserID) {

    const cdt = {
        _id: { '$in': arrUserID }
    };
    let res = await userModel.remove(cdt);
    // if (res.result.n == 0) {
    //     return ctx.error(Code.RET_USER_DELETE_ERR_USER);
    // }
    await doAfterDelete(arrUserID);
}
/**
 * 删除用户信息
 */
async function del(ctx) {
    const params = ctx.request.body;
    // 校验参数
    if (!params.user_id || params.user_id.length === 0) {
        return ctx.error(Code.RET_USER_DELETE_ERR_PARAM);
    }
    const arrUserID = params.user_id;
    await del_internal(arrUserID);

    ctx.success(Code.RET_USER_DELETE_OK);
}

/**
 * 审核访客信息
 */
async function verify(ctx) {
    const params = ctx.request.body;
    // 校验参数
    if (!params.user_id && !params.verify_flag) {
        return ctx.error(Code.RET_USER_VERIFY_ERR_PARAM);
    }
    // 同意时，需要设定访问期限，为了门禁安全
    if (params.verify_flag === Cons.USER_STATUS_OK && !params.access_from && !params.access_to) {
        return ctx.error(Code.RET_USER_VERIFY_ERR_PARAM);
    }

    const cdt = {
        _id: params.user_id,
        u_tp: Cons.USER_TYPE_VIP,
        u_st: Cons.USER_STATUS_PENDING
    };
    const user = await userModel.findOne(cdt);
    if (!user) {
        return ctx.error(Code.RET_USER_VERIFY_ERR_PARAM);
    }

    // 同意
    if (params.verify_flag === Cons.USER_STATUS_OK) {
        const upd = {
            u_st: Cons.USER_STATUS_PENDING,
            p_fr: params.access_from,
            p_to: params.access_to
        };
        await userModel.update(cdt, upd);
    }
    // 拒绝
    else if (params.verify_flag === Cons.USER_STATUS_PENDING) {
        await userModel.remove(cdt);
    }
    ctx.success(Code.RET_USER_VERIFY_OK);
}

/**
 * 查询员工信息
 */
async function query(ctx) {
    const params = ctx.request.query;
    // 校验参数
    if (!params.page_num || !params.page_size) {
        return ctx.error(Code.RET_USER_QUERY_ERR_PARAM);
    }
    if (params.type && params.type !== Cons.WEB_USER_TYPE_STAFF && params.type !== Cons.WEB_USER_TYPE_GUEST
        && params.type !== Cons.WEB_USER_TYPE_VIP && params.type !== Cons.WEB_USER_TYPE_STRANGER && params.type !== Cons.WEB_USER_TYPE_STUDENT) {
        return ctx.error(Code.RET_USER_QUERY_ERR_PARAM);
    }

    let cdt = {
        u_tp: { '$in': [Cons.USER_TYPE_STAFF, Cons.USER_TYPE_TRAINEE] }
    };
    let sort = { 'u_dm': 1, 'u_tp': 1, 'u_no': 1 };
    if (params.type && params.type === Cons.WEB_USER_TYPE_GUEST) {
        cdt.u_tp = { '$in': [Cons.USER_TYPE_GUEST] };
        sort = { 'u_tp': -1, 'u_nm': 1 };
    } if (params.type && params.type === Cons.WEB_USER_TYPE_STUDENT) {
        cdt.u_tp = { '$in': [Cons.USER_TYPE_STUDENT] };
        sort = { 'u_tp': -1, 'u_nm': 1 };
    } else if (params.type && params.type === Cons.WEB_USER_TYPE_VIP) {
        cdt.u_tp = { '$in': [Cons.USER_TYPE_VIP] };
        sort = { 'u_tp': -1, 'u_nm': 1 };
    } else if (params.type && params.type === Cons.WEB_USER_TYPE_STRANGER) {
        cdt.u_tp = { '$in': [Cons.USER_TYPE_STRANGER] };
        sort = { 'u_tp': -1, 'c_at': -1 };
    } 
    if (params.user_name) {
        cdt.u_nm = { $regex: params.user_name };
    }
    const users = await Util.pageQuery(params.page_num, params.page_size, userModel,
        cdt, '', '', sort);

    let rst = [];
    if (users && users.results && users.results.length > 0) {
        const userTypes = await Config.getUserType();
        const depts = await Config.getDeptList();
        for (let i = 0; i < users.results.length; i++) {
            const item = users.results[i];
            // TODO 因为图片的存储暂时都是直接放在数据库的，所以提供了头像检索的专用接口，以提高效率
            // const image = Util.isEmpty(item.u_hp) ? '' : item.u_hp.toString('base64');
            // const feature = [image, image, image];
            if (item.u_tp === Cons.USER_TYPE_STAFF || item.u_tp === Cons.USER_TYPE_TRAINEE) {
                rst.push({
                    user_id: item._id,
                    user_name: item.u_nm,
                    user_sex: item.u_sx,
                    user_avatar: Util.getAvatarUrl(item.u_hp),
                    user_type: item.u_tp,
                    user_phone: item.u_pn,
                    user_email: item.u_em,
                    access_from: item.p_fr ? Util.fmtDate(item.p_fr, 'YYYYMMDD') : '',
                    access_to: item.p_to ? Util.fmtDate(item.p_to, 'YYYYMMDD') : '',
                    user_mark: Base64.decode(item.g_mk),
                    user_mark2: Base64.decode(item.g_mk2),
                    user_no: item.u_no,
                    user_dept: item.u_dm,
                    user_dept_disp: Config.getConfigLabel(depts, item.u_dm),
                    user_position: item.u_pt
                });
            } else if(item.u_tp === Cons.USER_TYPE_STUDENT){
                rst.push({
                    // 学生
                    _id: item._id,
                    user_name: item.u_nm,
                    user_sex: item.u_sx,
                    user_avatar: item.u_hp,
                    user_phone: item.u_pn,
                    user_email: item.u_em,
                    // access_from: item.p_fr ? Util.fmtDate(item.p_fr, 'YYYYMMDD') : '',
                    // access_to: item.p_to ? Util.fmtDate(item.p_to, 'YYYYMMDD') : '',
                    // user_mark: Base64.decode(item.g_mk),
                    // user_mark2: Base64.decode(item.g_mk2),
                    user_num: item.s_num,
                    user_school: item.s_school,
                    user_class: item.s_class,
                    feature_id:item.feature_id
                });
            }
            else {
                rst.push({
                    user_id: item._id,
                    user_name: item.u_nm,
                    user_sex: item.u_sx,
                    user_avatar: Util.getAvatarUrl(item.u_hp),
                    user_type: item.u_tp,
                    user_type_disp: Config.getConfigLabel(userTypes, item.u_tp),
                    user_phone: item.u_pn,
                    user_email: item.u_em,
                    access_from: item.p_fr ? Util.fmtDate(item.p_fr, 'YYYYMMDD') : '',
                    access_to: item.p_to ? Util.fmtDate(item.p_to, 'YYYYMMDD') : '',
                    user_mark: Base64.decode(item.g_mk),
                    user_mark2: Base64.decode(item.g_mk2),
                    user_company: item.g_cp,
                    user_receptionist: item.g_re,
                    create_at: Util.fmtDate(item.c_at, 'YYYY/MM/DD HH:mm:ss')
                });
            }
        }
    }
    ctx.success(Code.RET_USER_QUERY_OK, {
        page_num: params.page_num,
        page_size: params.page_size,
        total: users.total,
        result: rst
    });
}

/**
 * 查询员工详情
 */
async function query_detail(ctx) {
    const params = ctx.request.query;
    // 校验参数
    if (!params.user_id) {
        return ctx.error(Code.RET_USER_QUERY_ERR_PARAM);
    }

    const user = await userModel.findOne({ _id: params.user_id });
    if (user) {
        const userTypes = await Config.getUserType();
        const depts = await Config.getDeptList();

        let rst = null;
        if (user.u_tp === Cons.USER_TYPE_STAFF || user.u_tp === Cons.USER_TYPE_TRAINEE) {
            rst = {
                user_id: user._id,
                user_name: user.u_nm,
                user_sex: user.u_sx,
                user_avatar: Util.getAvatarUrl(user.u_hp),
                user_type: user.u_tp,
                user_type_disp: Config.getConfigLabel(userTypes, user.u_tp),
                user_phone: user.u_pn,
                user_email: user.u_em,
                access_from: user.p_fr ? Util.fmtDate(user.p_fr, 'YYYYMMDD') : '',
                access_to: user.p_to ? Util.fmtDate(user.p_to, 'YYYYMMDD') : '',
                user_mark: Base64.decode(item.g_mk),
                user_mark2: Base64.decode(item.g_mk2),
                user_no: user.u_no,
                user_dept: user.u_dm,
                user_dept_disp: Config.getConfigLabel(depts, user.u_dm),
                user_position: user.u_pt
            };
        } else {
            rst = {
                user_id: user._id,
                user_name: user.u_nm,
                user_sex: user.u_sx,
                user_avatar: Util.getAvatarUrl(user.u_hp),
                user_type: user.u_tp,
                user_type_disp: Config.getConfigLabel(userTypes, user.u_tp),
                user_phone: user.u_pn,
                user_email: user.u_em,
                access_from: user.p_fr ? Util.fmtDate(user.p_fr, 'YYYYMMDD') : '',
                access_to: user.p_to ? Util.fmtDate(user.p_to, 'YYYYMMDD') : '',
                user_mark: Base64.decode(user.g_mk),
                user_mark2: Base64.decode(user.g_mk2),
                user_company: user.g_cp,
                user_receptionist: user.g_re
            };
        }
        return ctx.success(Code.RET_USER_QUERY_OK, rst);
    } else {
        return ctx.error(Code.RET_USER_QUERY_ERR_NOT_EXIST);
    }
}

/**
 * 查询员工显示头像和特征头像
 */
async function query_avatar(ctx) {
    const params = ctx.request.query;
    // 校验参数
    if (!params.user_id) {
        return ctx.error(Code.RET_USER_QUERY_AVATAR_ERR_PARAM);
    }

    // 检验id是否正确
    const cdt = {
        _id: params.user_id
    };
    const user = await userModel.findOne(cdt);
    if (!user) {
        return ctx.error(Code.RET_USER_QUERY_AVATAR_NOT_EXIST);
    }

    let rst = [];
    // const image = Util.isEmpty(user.u_hp) ? '' : user.u_hp.toString('base64');
    // 压缩一把 20171209从今天开始头像存储url
    // const image = Util.isEmpty(user.u_hp) ? '' : (await ImageUtil.resize(user.u_hp, 100, 100)).toString('base64');
    const feature = [];

    // 人脸特征 默认最多显示5张
    const faceFeature = await faceFeatureModel.findOne(cdt);
    if (faceFeature && faceFeature.features && faceFeature.features[featureModel]
        && faceFeature.features[featureModel].length > 0) {
        let len = 5;
        if (faceFeature.features[featureModel].length < len) {
            len = faceFeature.features[featureModel].length;
        }
        for (let i = 0; i < len; i++) {
            const item = faceFeature.features[featureModel][i];
            if (item.image.data && item.image.data.buffer) {
                feature.push(
                    fs.readFileSync(item.image.data.buffer).toString('base64')
                    // (await ImageUtil.resize(item.image.data.buffer, 100, 100)).toString('base64')
                );
            }
        }
    }

    rst.push({
        // user_avatar: image,
        user_feature: feature
    });
    ctx.success(Code.RET_USER_QUERY_AVATAR_OK, rst);
}

/**
 * 查询人脸特征
 */
async function query_feature(ctx) {
    const params = ctx.request.query;
    // 校验参数
    if (!params.user_id) {
        return ctx.error(Code.RET_USER_QUERY_FEATURE_ERR_PARAM);
    }

    const cdt = {
        _id: params.user_id
    };
    const user = await userModel.findOne(cdt);
    if (!user) {
        return ctx.error(Code.RET_USER_QUERY_FEATURE_ERR_NOT_EXIST);
    }

    let rst = [];
    // 人脸特征
    const faceFeature = await faceFeatureModel.findOne(cdt);
    if (faceFeature && faceFeature.features && faceFeature.features[featureModel] && faceFeature.features[featureModel].length > 0) {
        for (let i = 0; i < faceFeature.features[featureModel].length; i++) {
            const item = faceFeature.features[featureModel][i];
            // 压缩一把
            if (item.image.data && item.image.data.buffer) {
                rst.push(
                    fs.readFileSync(item.image.data.buffer).toString('base64')
                    // (await ImageUtil.resize(item.image.data.buffer, 480)).toString('base64')
                );
            }
        }
    }

    ctx.success(Code.RET_USER_QUERY_FEATURE_OK, rst);
}

/**
 * 特征注册
 */
async function regFeature(ctx) {
    const params = ctx.request.body;
    // 校验参数
    if (!params.user_id || !params.feature_pic || params.feature_pic.length === 0) {
        return ctx.error(Code.RET_RECOG_REGISTER_ERR_PARAM);
    }
    const has = await userModel.findOne({ '_id': params.user_id });
    if (!has) {
        return ctx.error(Code.RET_RECOG_REGISTER_NO_USER);
    }
    // 因为目前特征不支持单个删除，所以先采用先全部删除，再重新添加的步骤
    let rst = await doDelete([params.user_id]);
    if (rst.code === 0) {
        rst = await doRegister(params.user_id, params.feature_pic);
        if (rst.code === 0) {
            return ctx.success(rst);
        } else {
            return ctx.error(rst);
        }
    } else {
        return ctx.error(Code.RET_RECOG_DELETE_ERR);
    }
}

/**
 * 特征删除
 */
async function delFeature(ctx) {
    const params = ctx.request.body;
    // 校验参数
    if (!params.user_id) {
        return ctx.error(Code.RET_RECOG_DELETE_ERR_PARAM);
    }

    const rst = await doDelete([params.user_id]);
    if (rst.code === 0) {
        return ctx.success(rst);
    } else {
        return ctx.error(rst);
    }
}

/**
 * 人脸识别
 */
async function recognize(ctx) {
    const params = ctx.request.body;
    // 校验参数
    if (!params.user_avatar) {
        return ctx.error(Code.RET_RECOG_RECOGNIZE_ERR_PARAM);
    }

    const userId = await doRecog(params.user_avatar);
    if (userId) {
        const user = await userModel.findOne({ _id: userId });
        ctx.success(Code.RET_RECOG_RECOGNIZE_OK, { userId: userId, userName: user.u_nm });
    } else {
        return ctx.error(Code.RET_RECOG_RECOGNIZE_ERR_NOT_EXIST);
    }
}

/**
 * 特征注册
 * @param userID
 * @param featurePic
 */
async function doRegister(userID, featurePic) {
    const param = { _id: userID, pictures: featurePic };
    const rst = await httpUtil.post(featureUrl.register, param);
    if (rst.is_success === 0) {
        let message = '';
        let flag = false;
        for (let index in rst.pic_results) {
            if (rst.pic_results[index] != 0) {
                let tag = Number(index) + 1;
                message += tag + ",";
                flag = true;
            }
        }
        if (flag) {
            message = message.substring(0, message.length - 1);
            return { code: Code.RET_RECOG_REGISTER_ERR.code, msg: `第${message}张照片有问题，请检查` };
        } else {
            return Code.RET_RECOG_REGISTER_OK;
        }
    } else {
        return Code.RET_RECOG_REGISTER_ERR;
    }
}

/**
 * 特征删除
 * @param arrUserID 【数组】支持多个删除
 */
async function doDelete(arrUserID) {
    const rst = await httpUtil.post(featureUrl.delete, { _id: arrUserID });
    if (rst.is_success === 0) {
        return Code.RET_RECOG_DELETE_OK;
    } else {
        return { code: Code.RET_RECOG_REGISTER_ERR.code, msg: rst.is_success + '' };
    }
}

/**
 * 人脸识别
 * @param picture
 */
async function doRecog(picture) {
    const param = {
        record: 0, //不保存识别记录
        request_recog_result: 'all',
        pictures: [{ type: 'jpeg', width: 128, height: 128, picdata: picture },
        { type: 'jpeg', width: 128, height: 128, picdata: picture },
        { type: 'jpeg', width: 128, height: 128, picdata: picture }]
    };
    const rst = await httpUtil.post(featureUrl.recognize, param);
    if (rst.is_success === 0) {
        return rst.face_info._id;
    } else {
        return '';
    }
}

/**
 * 显示头像保存
 * 保存的文件名依然是用户ID，但是为了兼顾缓存问题，存到数据库的时候加一个时间戳
 */
function saveAvatar(buffer, origfile, newfile) {
    let url = '';
    try {
        const origPath = resConfig.avatar_dir + origfile;
        if (origfile && fs.existsSync(origPath)) {
            fs.unlinkSync(origPath);
        }
        const newpath = resConfig.avatar_dir + newfile;
        fs.writeFileSync(newpath, buffer);

        // url = resConfig.base_pic_uri + '/pic/' + newfile;
        url = newfile; // url的路径前缀不保存了
    } catch (err) {
        return logger.logError('\n' + '===== SaveAvatar Error - ' + err + '\n');
    }

    let time = new Date().getTime();
    return `${url}?${time}`;
}

//==============以下接口APP专供==============
/**
 * 查询员工信息
 */
async function query_users(ctx) {
    const params = ctx.request.query;
    // 校验参数
    if (!params.user_type) {
        return ctx.error(Code.RET_USER_QUERY_ERR_PARAM);
    }
    if (params.user_type != Cons.APP_USER_TYPE_STAFF
        && params.user_type != Cons.APP_USER_TYPE_GUEST
        && params.user_type != Cons.APP_USER_TYPE_VIP) {
        return ctx.error(Code.RET_USER_QUERY_ERR_PARAM);
    }

    let cdt = {
        u_tp: { '$in': [Cons.USER_TYPE_STAFF, Cons.USER_TYPE_TRAINEE] }
    };
    if (params.user_type == Cons.APP_USER_TYPE_VIP) {
        cdt.u_tp = Cons.USER_TYPE_VIP;
    } else if (params.user_type == Cons.APP_USER_TYPE_GUEST) {
        cdt.u_tp = Cons.USER_TYPE_GUEST;
    }
    const users = await userModel.find(cdt).sort({ 'u_no': 1 });

    let rst = [];
    if (users && users.length > 0) {
        const depts = await Config.getDeptList();
        for (let i = 0; i < users.length; i++) {
            const item = users[i];
            let type = Cons.APP_USER_TYPE_STAFF;
            if (item.u_tp == Cons.USER_TYPE_VIP) {
                type = Cons.APP_USER_TYPE_VIP;
            } else if (item.u_tp == Cons.USER_TYPE_GUEST) {
                type = Cons.APP_USER_TYPE_GUEST;
            }
            if (type == Cons.APP_USER_TYPE_STAFF) {
                rst.push({
                    user_id: item._id,
                    user_name: item.u_nm,
                    user_sex: item.u_sx,
                    user_avatar: Util.getAvatarUrl(item.u_hp),
                    user_type: type,
                    user_phone: item.u_pn,
                    user_email: item.u_em,
                    access_from: item.p_fr ? Util.fmtDate(item.p_fr, 'YYYYMMDD') : '',
                    access_to: item.p_to ? Util.fmtDate(item.p_to, 'YYYYMMDD') : '',
                    user_mark: Base64.decode(item.g_mk),
                    user_mark2: Base64.decode(item.g_mk2),
                    user_no: item.u_no,
                    user_dept: item.u_dm,
                    user_dept_disp: Config.getConfigLabel(depts, item.u_dm),
                    user_position: item.u_pt
                });
            } else {
                rst.push({
                    user_id: item._id,
                    user_name: item.u_nm,
                    user_sex: item.u_sx,
                    user_avatar: Util.getAvatarUrl(item.u_hp),
                    user_type: type,
                    user_phone: item.u_pn,
                    user_email: item.u_em,
                    access_from: item.p_fr ? Util.fmtDate(item.p_fr, 'YYYYMMDD') : '',
                    access_to: item.p_to ? Util.fmtDate(item.p_to, 'YYYYMMDD') : '',
                    user_mark: Base64.decode(item.g_mk),
                    user_mark2: Base64.decode(item.g_mk2),
                    user_company: item.g_cp,
                    user_receptionist: item.g_re
                });
            }
        }
    }
    ctx.success(Code.RET_USER_QUERY_OK, rst);
}

// /**
//  * 查询员工头像
//  */
// async function query_image(ctx) {
//     const params = ctx.request.query;
//         // 校验参数
//         if (!params.user_id) {
//             return ctx.error(Code.RET_APP_QUERY_IMAGE_ERR_PARAM);
//         }
//
//         // 检验id是否正确
//         const user = await userModel.findOne({_id: params.user_id});
//         if (!user) {
//             return ctx.error(Code.RET_APP_QUERY_IMAGE_NOT_EXIST);
//         }
//         let img = user.u_hp;
//         if (Util.isEmpty(img)) {
//             img = fs.readFileSync('server/controllers/img/default_avatar.png');
//         }
//         ctx.imgae(img);
// }

/**
 * 新增访客
 */
async function reg_guest(ctx) {
    const params = ctx.request.body;
    // 校验参数
    if (!params.user_type || !params.user_name) {
        return ctx.error(Code.RET_GUEST_REGISTER_ERR_PARAM);
    }
    if (params.user_type != Cons.USER_TYPE_GUEST && params.user_type != Cons.USER_TYPE_VIP) {
        return ctx.error(Code.RET_GUEST_REGISTER_ERR_PARAM);
    }

    // 跟WEB端逻辑一致
    await register(ctx);
}

/**
 * 通过内部调用新增陌生人
 * @param params.user_type
 * @param params.user_avatar
 * @param params.avatar_format
 * @param params.user_name
 * @param params.user_sex
 * @return 结果及id
 */
async function reg_stranger(params) {
    let res = {
        result: true,
        id: ""
    };
    if (params.user_type != Cons.USER_TYPE_STRANGER) {
        res.result = false
        return res;
    }
    let model = {
        u_nm: params.user_name,
        u_tp: params.user_type,
        u_st: Cons.USER_STATUS_OK
    };
    model._id = new mongoose.Types.ObjectId();
    res.id = model._id;
    if (!Util.isEmpty(params.user_avatar)) {
        const newfile = model._id + '.jpeg';
        model.u_hp = saveAvatar(Buffer.from(params.user_avatar, 'base64'), '', newfile);
    }
    if (!Util.isEmpty(params.user_sex)) {
        model.u_sx = params.user_sex;
    }
    await userModel.create(model);                      //创建用户

    let featurePic = [{
        picdata: params.user_avatar,
        type: params.avatar_format
    }];

    const rst = await httpUtil.post(featureUrl.register,
        { _id: model._id, pictures: featurePic });    //注册特征
    logger.logInfo('===== reg_stranger feature register - ' + JSON.stringify(rst) + ' =====');
    let regFailed = false;
    if (rst.is_success != 0) {
        regFailed = true;
    } else {
        for (let index in rst.pic_results) {
            if (rst.pic_results[index] != 0) {
                regFailed = true;
                break;
            }
        }
    }
    if (regFailed) {
        res.result = false;
        await userModel.remove({ _id: model._id });
    }
    return res;
}

/**
 * 删除访客（对APP开放）
 */
async function del_guest(ctx) {
    const params = ctx.request.body;
    // 校验参数
    if (!params.user_id) {
        return ctx.error(Code.RET_GUEST_DELETE_ERR_PARAM);
    }

    const cdt = {
        _id: params.user_id,
        u_tp: { '$in': [Cons.USER_TYPE_GUEST, Cons.USER_TYPE_VIP] }
    };

    const user = await userModel.findOne(cdt);
    if (!user) {
        return ctx.error(Code.RET_GUEST_DELETE_ERR_NOT_EXIST);
    }

    await userModel.remove(cdt);
    await doAfterDelete([params.user_id]); // APP暂时只支持单个删除
    ctx.success(Code.RET_GUEST_DELETE_OK);
}

/**
 * 删除用户之后的相关处理
 */
async function doAfterDelete(arrUserID) {
    // 【1】同时把识别记录都变成陌生人，不然识别记录不好看了。因为涉及大量的update操作，这一步不需要了业务上也不是很合理。
    await change_record_user_type(arrUserID, Cons.USER_TYPE_STRANGER);

    // 【2】删除特征。因为特征的管理跟后台接口不是同一个服务，所以暂时放在前端做。
    await doDelete(arrUserID);

    // 【3】删除门禁权限
    await guardModel.update({}, { $pull: { u_in: { u_id: arrUserID } } }, { multi: true });

    // 【4】删除头像 先不删除了，这样离职员工的记录可以看到头像
    // const filename = user._id + Util.getFileExt(ctx.req.file.originalname);
    // const filepath = resConfig.avatar_dir + filename;
    // if (fs.existsSync(filepath)) {
    //     fs.unlinkSync(filepath);
    // }
}

async function change_record_user_type(id, type) {
    const record_cdt = {
        u_id: { '$in': id }
    };
    const record_upd = {
        u_tp: type
    };

    await UserRecordModel.update(record_cdt, { '$set': record_upd }, { "multi": true });
}

async function clean(ctx) {
    let params = ctx.request.body;
    let types = params.types;
    const upd = {
        u_tp: { '$in': types }
    };
    let users = await userModel.find(upd);
    let rlist = [];
    users.map((user) => {
        rlist.push(user._id);
    });
    await del_internal(rlist);
    ctx.success(Code.RET_USER_CLEAN_OK);
}

// 新增学生
async function add_student(ctx) {
    const isDev = ctx.request.host.indexOf('localhost')>-1
    const params = ctx.request.body;
    // 校验参数
    if (!params.user_name || !params.user_num) {
        return ctx.error(Code.REQUEST_ERROR('学生参数错误'));
    }
    const data = await userModel.findOne({s_num:params.user_num,u_tp:Cons.USER_TYPE_STUDENT})
    if(data){
        return ctx.error(Code.REQUEST_ERROR('学号不能重复'))
    }
    const snumReg = /^[\da-zA-Z]{1,20}$/
    if(!snumReg.test(params.user_num)){
        return ctx.error(Code.REQUEST_ERROR('学号格式错误，合法字符为字母或数字，且不能超过20位'))
    }
    let model = {
        u_nm: params.user_name,
        u_tp: Cons.USER_TYPE_STUDENT,//5学生
        u_hp: params.user_avatar,
        u_sx: params.user_sex || 0,
        u_pn: params.user_phone,
        u_em: params.user_email,

        s_num: params.user_num,
        s_school: params.user_school,
        s_class: params.user_class
    };
    model._id = new mongoose.Types.ObjectId();
    const student = await userModel.create(model);
    let asyncAvatarToLibrayQueue = []; //保存照片到底库的任务集合
    if(params.user_avatar){
        asyncAvatarToLibrayQueue.push({
            student,
            studentAvatar:params.user_avatar
        })
    }
    if(asyncAvatarToLibrayQueue.length){
        ctx.success(Code.REQUEST_SUCCESS('添加成功，设备底库照片正在后台更新'))
    }else{
        return ctx.success(Code.REQUEST_SUCCESS('添加成功'))
    }

    let logtemp
    if(asyncAvatarToLibrayQueue&&asyncAvatarToLibrayQueue.length){
        logtemp = await batchLogModel.create({
            type_name:'上传学员头像',
            content:''
        })
    }
    // 执行缓存的任务
    setTimeout(async ()=>{
        const devices = await ipcModel.find({device_uuid:{$ne:null},register_session:{$ne:null}})
        const deviceErrorCount = {}; //标记设备是否可用
        const deviceSuccessCount = {}; //成功数量统计
        devices.forEach(child=>{
            deviceSuccessCount[child.d_nm+'('+child.device_num+')'] = 0
        })
        let logMsg = `共${asyncAvatarToLibrayQueue.length}张照片信息，设备数量共${devices.length}台。`
        await batchLogModel.updateOne({_id:logtemp._id},{content:`${logMsg}正在同步中。。。`})
        let ErrorMsg = ''
        for(let queue of asyncAvatarToLibrayQueue){
            const resSave = await save_student_library(queue.student,queue.studentAvatar,isDev,devices,deviceErrorCount,deviceSuccessCount)
            if(resSave&&resSave.isDeviceError){
                ErrorMsg+=`<div style="text-align:left">${resSave.deviceErrorMsg}</div>`
            }
        }
        let resLog = Object.keys(deviceSuccessCount).map(key=> `${key}:成功${deviceSuccessCount[key]}张`).join(';') + '。'
        logMsg += resLog
        if(ErrorMsg){
            logMsg += `<div style="text-align:center">=====错误信息汇总=====</div>\r\n${ErrorMsg}`
        }
        await batchLogModel.updateOne({_id:logtemp._id},{content:logMsg})
    },1000)
}

// 批量上传学生
async function upload_student(ctx) {
    try{
        const file = ctx.req.file.buffer;
        const workBook = XLSX.read(file);
        const workSheet = workBook.Sheets['Sheet1'];
        const tableData = XLSX.utils.sheet_to_json(workSheet)
        // 提取的字段
        const tableKeyMap = {
            '姓名': 'u_nm',
            '学号': 's_num',
            '性别': 'u_sx',
            '电话（可选）': 'u_pn',
            '学校（可选）': 's_school',
            '班级（可选）': 's_class',
            '邮箱（可选）':'u_em',
        };
        const tableDataStandard = [];
        for (let i = 0; i < tableData.length; i++) {
            const item = tableData[i];
            const dataTemp = {};
            const phone_regex = /^1[3456789]\d{9}$/
            const email_regex = /^\w+@\w{2,6}\.[a-zA-Z]{2,6}$/
            const snumReg = /^[\da-zA-Z]{1,20}$/
            for (let column in item) {
                const formatColumn = column.toString().trim();
                tableKeyMap[formatColumn] && (dataTemp[tableKeyMap[formatColumn]] = item[column])
                const v = item[column].trim();
                if (column == '性别' && (v!=='男'&&v!=='女')) {
                    return ctx.error({
                        code: Code.RET_USER_REGISTER_BATCH_ERR_PARAM.code,
                        msg: util.format(Code.RET_USER_REGISTER_BATCH_ERR_PARAM.msg,
                            util.format('第%s行 的性别不合法', i + 1))
                    });
                }
                if (column == '学号' && !snumReg.test(v)) {
                    return ctx.error({
                        code: Code.RET_USER_REGISTER_BATCH_ERR_PARAM.code,
                        msg: util.format(Code.RET_USER_REGISTER_BATCH_ERR_PARAM.msg,
                            util.format('第%s行 的学号格式错误', i + 1))
                    });
                }
                if (column == '电话（可选）' && v && !phone_regex.test(v)) {
                    return ctx.error({
                        code: Code.RET_USER_REGISTER_BATCH_ERR_PARAM.code,
                        msg: util.format(Code.RET_USER_REGISTER_BATCH_ERR_PARAM.msg,
                            util.format('第%s行 的电话格式不合法', i + 1))
                    });
                }
                if (column == '邮箱（可选）' && v && !email_regex.test(v)) {
                    return ctx.error({
                        code: Code.RET_USER_REGISTER_BATCH_ERR_PARAM.code,
                        msg: util.format(Code.RET_USER_REGISTER_BATCH_ERR_PARAM.msg,
                            util.format('第%s行 的邮箱格式不合法', i + 1))
                    });
                }
            }
            //校验是否缺少必选项
            for (var key of Object.keys(dataTemp)) {
                if (!dataTemp[key].trim()) {
                    delete dataTemp[key]
                }
            }
            let column_name = '';
            if (!dataTemp.u_nm) {
                column_name = '学生姓名';
            } else if (!dataTemp.s_num) {
                column_name = '学号';
            } else if (!dataTemp.u_sx) {
                column_name = '性别';
            } else {
                dataTemp.u_sx = dataTemp.u_sx=='女'?0:dataTemp.u_sx=='男'?1:0
            }
            if (column_name) {
                return ctx.error({
                    code: Code.RET_USER_REGISTER_BATCH_ERR_PARAM.code,
                    msg: util.format(Code.RET_USER_REGISTER_BATCH_ERR_PARAM.msg,
                        util.format('第%s行 %s 是空值', i + 1, column_name))
                });
            }else{
                dataTemp._id = new mongoose.Types.ObjectId();
                dataTemp.u_tp = 5
                tableDataStandard.push(dataTemp)
            }
        }
        // 转换完成的数据，已经通过校验，直接插入数据库
        for(let item of tableDataStandard){
            const data = await userModel.findOne({s_num:item.s_num})
            if(data){
                return ctx.error(Code.REQUEST_ERROR(`学号:${item.s_num}已存在`))
            }      
            // 新增学生
            await userModel.create(item);
        }
        if(tableDataStandard.length===0){
            return ctx.error(Code.REQUEST_ERROR('无有效数据'));
        }
        ctx.success(Code.REQUEST_SUCCESS('导入成功'));
    }catch(err){
        console.log(err)
        ctx.error()
    }
}

// 学生信息编辑
async function update_student(ctx){
    const isDev = ctx.request.host.indexOf('localhost')>-1
    const params = ctx.request.body
    try{
        const studentParams = {
            user_name: '姓名',
            user_num: '学号',
            _id: "缺少学生对应的id关键字"
        }
        const schema = Object.keys(studentParams)
        if(!params || typeof params !=='object') return ctx.error(Code.REQUEST_ERROR('请填写学生信息'))
        for(let key of schema){
            if(params[key]==undefined||params[key]===''){
                const msg = studentParams[key]
                return ctx.error(Code.REQUEST_ERROR(msg))
            }
        }
        const snumReg = /^[\da-zA-Z]{1,20}$/
        if(!snumReg.test(params.user_num)){
            return ctx.error(Code.REQUEST_ERROR('学号格式错误，合法字符为字母或数字，且不能超过20位'))
        }
        const updateParams = {
            u_nm: params.user_name,
            u_hp: params.user_avatar,
            u_sx: params.user_sex,
            u_pn: params.user_phone,
            u_em: params.user_email,

            s_num: params.user_num,
            s_school: params.user_school,
            s_class: params.user_class
        }
        const validData = await userModel.findOne({
            _id:{
                $ne: params._id
            },
            s_num: params.user_num
        })
        if(validData) return ctx.error(Code.REQUEST_ERROR('学号重复'))
        const data = await userModel.findByIdAndUpdate(params._id,updateParams)
        let asyncAvatarToLibrayQueue = []; //保存照片到底库的任务集合
        if(data){     
            //处理头像
            if(params.user_avatar){   
                asyncAvatarToLibrayQueue.push({
                    student:data,
                    studentAvatar:params.user_avatar
                })
            }
        }else{
            return ctx.error(Code.REQUEST_ERROR('未找到对应的学生'))
        }
        if(asyncAvatarToLibrayQueue.length){
            ctx.success(Code.REQUEST_SUCCESS('操作成功，设备底库照片正在后台更新'))
        }else{
            return ctx.success(Code.REQUEST_SUCCESS('更新成功'))
        }

        let logtemp
        if(asyncAvatarToLibrayQueue&&asyncAvatarToLibrayQueue.length){
            logtemp = await batchLogModel.create({
                type_name:'更新学员到底库',
                content:''
            })
        }
        // 执行缓存的任务
        setTimeout(async ()=>{
            const devices = await ipcModel.find({device_uuid:{$ne:null},register_session:{$ne:null}})
            const deviceErrorCount = {}; //标记设备是否可用
            const deviceSuccessCount = {}; //成功数量统计
            devices.forEach(child=>{
                deviceSuccessCount[child.d_nm+'('+child.device_num+')'] = 0
            })
            let logMsg = `共${asyncAvatarToLibrayQueue.length}张照片信息，设备数量共${devices.length}台。`
            await batchLogModel.updateOne({_id:logtemp._id},{content:`${logMsg}正在同步中。。。`})
            let ErrorMsg = ''
            for(let queue of asyncAvatarToLibrayQueue){
                const resSave = await update_student_library(queue.student,queue.studentAvatar,isDev,devices,deviceErrorCount,deviceSuccessCount)
                if(resSave&&resSave.isDeviceError){
                    ErrorMsg+=`<div style="text-align:left">${resSave.deviceErrorMsg}</div>`
                }
            }
            let resLog = Object.keys(deviceSuccessCount).map(key=> `${key}:成功${deviceSuccessCount[key]}张`).join(';') + '。'
            logMsg += resLog
            if(ErrorMsg){
                logMsg += `<div style="text-align:center">=====错误信息汇总=====</div>\r\n${ErrorMsg}`
            }
            await batchLogModel.updateOne({_id:logtemp._id},{content:logMsg})
        },1000)
    }catch(err){
        console.log(err)
        return ctx.error(Code.REQUEST_ERROR('更新失败'))
    }
}

// 删除学生
async function delete_student(ctx){
    const params = ctx.request.body
    try{
        if(!params || typeof params !=='object' || !params._id) return ctx.error(Code.REQUEST_ERROR('请选择需要删除的学生'))
        const ids = Array.isArray(params._id)?params._id:[params._id]
        const devices = await ipcModel.find({device_uuid:{$ne:null},register_session:{$ne:null}})
        const asyncTaskQueue = [],deviceErrorCount={};
        const isDev = ctx.request.host.indexOf('localhost')>-1;

        for(let student_id of ids){
            const data = await userModel.findByIdAndRemove(student_id,'_id feature_id')
            // 后台异步任务
            if(data && data._id){
                asyncTaskQueue.push({
                    student:data
                })
            }
        }
        setTimeout(async ()=>{
            for(let queue of asyncTaskQueue){
                // 删除设备中的头像
                for(let device of devices){
                    if(deviceErrorCount[device._id]>5) continue //单个设备失败大于五次，就不再尝试
                    try{
                        const res = await httpUtil.postWithTimeout(`http://${isDev?device.device_ip:'127.0.0.1'}:8011/Request`,{
                            Name:'personListRequest',
                            UUID:device.device_uuid,
                            ChannelNo: device.channel_num,
                            Session: device.register_session,
                            TimeStamp:Math.floor(+new Date()/1000),
                            Sign:'',
                            Data:{
                                Action:'deletePerson',
                                PersonType:2,
                                PersonId: queue.student._id,
                            },
                        },10*1000)
                        if(res.Code===2){
                            // 超时
                            deviceErrorCount[device._id] ? deviceErrorCount[device._id]++ : deviceErrorCount[device._id]=1
                        }
                    }catch{
                        deviceErrorCount[device._id] ? deviceErrorCount[device._id]++ : deviceErrorCount[device._id]=1
                    }
                }
                // 清空特征值
                if(queue.student.feature_id){
                    try{
                        await newFaceFeatureModel.remove({_id:queue.student.feature_id})
                        await httpUtil.postWithTimeout(`${globalConfig.faceRecognitionServer}/facerec-ipc`, {
                            operate_cmd:2 ,//#1 增加，2 删除 ，3.获取人脸识别结果， 4.获取该图片中人脸特征向量（默认一个最大人脸） 5清空 6同步
                            group_id:"-1",// #默认为-1，为待识别人脸所在的库id
                            feature_id:queue.student.feature_id, //默认为-1， 只有在增加、删除操作有值,对数据库操作时需要确定修改的标识，(该值对应是某个人的id还是某一个人脸特征id需要考虑，并且单独个别修改还是批量修改该如何操作)
                        })
                    }catch(err){
                        console.log(err)
                    }
                }
            }
        },1000)
        return ctx.success(Code.REQUEST_SUCCESS('删除成功'))
    }catch(err){
        console.log(err)
        return ctx.error(Code.REQUEST_ERROR('删除失败'))
    }
}

async function get_student_by_id(ctx){
    const params = ctx.request.query
    try{
        if(!params || typeof params !=='object' || !params.id) return ctx.error(Code.REQUEST_ERROR('请选择相应学生'))
        const data = await userModel.findById(params.id)
        if(data){ 
            ctx.success(Code.REQUEST_SUCCESS('查看成功'),data)  
        }else{
            ctx.error(Code.REQUEST_ERROR('未找到对应的学生'))
        }
    }catch(err){
        console.log(err)
        return ctx.error(Code.REQUEST_ERROR('查看失败'))
    }
}

// 保存学员到底库
async function save_student_library(student,avatar,isDev,devices,deviceErrorCount,deviceSuccessCount){
    let isDeviceError = false,deviceErrorMsg = `学员-${student.u_nm}:`;
    // const devices = await ipcModel.find({device_uuid:{$ne:null},register_session:{$ne:null}})
    // 保存图片结果
    function formatSaveRes(res,device){
        let isError=false,isExist=false,isTimeout=false,errMsg='';
        if(res.Code===1){
            return isError
        }else if(res.Code===2){
            isTimeout = true
        }else if(res.Code === 1110 && res.Data.Result === -3){
            // 文件名字编号重复，新增操作时才会出现的错误码
            isExist = true
        }else if(res.Code === 1110 && res.Data.Result === -7){
            isError = true
            errMsg+=`设备-${device.d_nm}(${device.device_num})文件过大;`
        }else if(res.Code === 1110 && res.Data.Result === -8){
            isError = true
            errMsg+=`设备-${device.d_nm}(${device.device_num})储存空间不足;`
        }else if(res.Code === 1110 && res.Data.Result === -13){
            isError = true
            errMsg+=`设备-${device.d_nm}(${device.device_num})图片质量差;`
        }else if(res.Code === 1110 && res.Data.Result === -15){
            isError = true
            errMsg+=`设备-${device.d_nm}(${device.device_num})检测人脸失败;`
        }else {
            isError = true
            errMsg+=`设备-${device.d_nm}(${device.device_num})保存失败;`
        }
        return {
            isError,
            isExist,
            isTimeout,
            errMsg
        }
    }
    for(let device of devices){
        try{
            // 每个设备超时5次就不再尝试
            if(deviceErrorCount[device._id]>5) {
                isDeviceError = true
                deviceErrorMsg+=`设备-${device.d_nm}(${device.device_num})保存超时;`
                continue 
            }
            let studentParams = {
                Name:'personListRequest',
                UUID:device.device_uuid,
                ChannelNo: device.channel_num,
                Session: device.register_session,
                TimeStamp:Math.floor(+new Date()/1000),
                Sign:'',
                Data:{
                    Action:'addPerson',
                    PersonType:2,
                    PersonInfo:{
                        PersonId: student._id,
                        PersonName: student.u_nm,
                        Sex: student.u_sx||0,
                        IDCard: student.user_idcard,
                        Nation: student.user_nation,
                        Birthday: student.user_birthday,
                        Phone: student.u_pn,
                        Address: student.user_address,
                        LimitTime: student.user_limit, 
                        PersonExtension:{
                            PersonCode1: student.s_num, //学号
                            PersonCode2: student.s_school, //学校
                            PersonCode3: student.s_class, //班级
                            PersonData1: student.u_em //邮箱
                        },
                        PersonPhoto: avatar
                    },
                },
            }
            const res = await httpUtil.postWithTimeout(`http://${isDev?device.device_ip:'127.0.0.1'}:8011/Request`,studentParams,1000*20)
            let formatRes = formatSaveRes(res,device)
            if(!formatRes){
                deviceSuccessCount[device.d_nm+'('+device.device_num+')']?deviceSuccessCount[device.d_nm+'('+device.device_num+')']++:deviceSuccessCount[device.d_nm+'('+device.device_num+')']=1
            }else if(formatRes.isTimeout){
                // 超时
                isDeviceError = true
                deviceErrorCount[device._id]?deviceErrorCount[device._id]++:deviceErrorCount[device._id]=1
                // deviceErrorMsg+=`${device.d_nm}(${device.device_num})`
                deviceErrorMsg+=`设备-${device.d_nm}(${device.device_num})保存超时;`
            }else if(formatRes.isExist){
                // 编辑
                studentParams.Data.Action = 'editPerson'
                const res2 = await httpUtil.postWithTimeout(`http://${isDev?device.device_ip:'127.0.0.1'}:8011/Request`,studentParams,1000*20)
                formatRes = formatSaveRes(res2,device)
                if(!formatRes){
                    deviceSuccessCount[device.d_nm+'('+device.device_num+')']?deviceSuccessCount[device.d_nm+'('+device.device_num+')']++:deviceSuccessCount[device.d_nm+'('+device.device_num+')']=1
                }else if(formatRes.isTimeout){
                    // 超时
                    isDeviceError = true
                    deviceErrorCount[device._id]?deviceErrorCount[device._id]++:deviceErrorCount[device._id]=1
                    deviceErrorMsg+=`设备-${device.d_nm}(${device.device_num})保存超时;`
                }else{
                    isDeviceError = true
                    deviceErrorMsg+= formatRes.errMsg
                }
            }else{
                isDeviceError = true
                deviceErrorMsg+= formatRes.errMsg
            }
        }catch(err){
            isDeviceError = true
            deviceErrorCount[device._id]?deviceErrorCount[device._id]++:deviceErrorCount[device._id]=1
            deviceErrorMsg+=`设备-${device.d_nm}(${device.device_num})保存失败;`
        }
    }
    return {
        isDeviceError,
        deviceErrorMsg
    }
}

// 更新学员到底库
async function update_student_library(student,avatar,isDev,devices,deviceErrorCount,deviceSuccessCount){
    let isDeviceError = false,deviceErrorMsg = `学员-${student.u_nm}:`;
    // const devices = await ipcModel.find({device_uuid:{$ne:null},register_session:{$ne:null}})
    // 更新图片结果
    function formatUpdateRes(res,device){
        let isError=false,isExist=true,isTimeout=false,errMsg='';
        if(res.Code===1){
            return isError
        }else if(res.Code===2){
            isTimeout = true
        }else if(res.Code === 1106){
            // personId不存在，编辑操作时才会出现的错误码
            isExist = false
        }else if(res.Code === 1110 && res.Data.Result === -7){
            isError = true
            errMsg+=`设备-${device.d_nm}(${device.device_num})文件过大;`
        }else if(res.Code === 1110 && res.Data.Result === -8){
            isError = true
            errMsg+=`设备-${device.d_nm}(${device.device_num})储存空间不足;`
        }else if(res.Code === 1110 && res.Data.Result === -13){
            isError = true
            errMsg+=`设备-${device.d_nm}(${device.device_num})图片质量差;`
        }else if(res.Code === 1110 && res.Data.Result === -15){
            isError = true
            errMsg+=`设备-${device.d_nm}(${device.device_num})检测人脸失败;`
        }else {
            isError = true
            errMsg+=`设备-${device.d_nm}(${device.device_num})保存失败;`
        }
        return {
            isError,
            isExist,
            isTimeout,
            errMsg
        }
    }
    for(let device of devices){
        try{
            // 每个设备超时5次就不再尝试
            if(deviceErrorCount[device._id]>5) {
                isDeviceError = true
                deviceErrorMsg+=`设备-${device.d_nm}(${device.device_num})保存超时;`
                continue 
            }
            let studentParams = {
                Name:'personListRequest',
                UUID:device.device_uuid,
                ChannelNo: device.channel_num,
                Session: device.register_session,
                TimeStamp:Math.floor(+new Date()/1000),
                Sign:'',
                Data:{
                    Action:'editPerson',
                    PersonType:2,
                    PersonInfo:{
                        PersonId: student._id,
                        PersonName: student.u_nm,
                        Sex: student.u_sx||0,
                        IDCard: student.user_idcard,
                        Nation: student.user_nation,
                        Birthday: student.user_birthday,
                        Phone: student.u_pn,
                        Address: student.user_address,
                        LimitTime: student.user_limit, 
                        PersonExtension:{
                            PersonCode1: student.s_num, //学号
                            PersonCode2: student.s_school, //学校
                            PersonCode3: student.s_class, //班级
                            PersonData1: student.u_em //邮箱
                        },
                        PersonPhoto: avatar
                    },
                },
            }
            const res = await httpUtil.postWithTimeout(`http://${isDev?device.device_ip:'127.0.0.1'}:8011/Request`,studentParams,1000*20)
            let formatRes = formatUpdateRes(res,device)
            if(!formatRes){
                deviceSuccessCount[device.d_nm+'('+device.device_num+')']?deviceSuccessCount[device.d_nm+'('+device.device_num+')']++:deviceSuccessCount[device.d_nm+'('+device.device_num+')']=1
            }else if(formatRes.isTimeout){
                // 超时
                isDeviceError = true
                deviceErrorCount[device._id]?deviceErrorCount[device._id]++:deviceErrorCount[device._id]=1
                // deviceErrorMsg+=`${device.d_nm}(${device.device_num})`
                deviceErrorMsg+=`设备-${device.d_nm}(${device.device_num})保存超时;`
            }else if(!formatRes.isExist){
                // 编辑
                studentParams.Data.Action = 'addPerson'
                const res2 = await httpUtil.postWithTimeout(`http://${isDev?device.device_ip:'127.0.0.1'}:8011/Request`,studentParams,1000*20)
                formatRes = formatUpdateRes(res2,device)
                if(!formatRes){
                    deviceSuccessCount[device.d_nm+'('+device.device_num+')']?deviceSuccessCount[device.d_nm+'('+device.device_num+')']++:deviceSuccessCount[device.d_nm+'('+device.device_num+')']=1
                }else if(formatRes.isTimeout){
                    // 超时
                    isDeviceError = true
                    deviceErrorCount[device._id]?deviceErrorCount[device._id]++:deviceErrorCount[device._id]=1
                    deviceErrorMsg+=`设备-${device.d_nm}(${device.device_num})保存超时;`
                }else{
                    isDeviceError = true
                    deviceErrorMsg+= formatRes.errMsg
                }
            }else{
                isDeviceError = true
                deviceErrorMsg+= formatRes.errMsg
            }
        }catch(err){
            isDeviceError = true
            deviceErrorCount[device._id]?deviceErrorCount[device._id]++:deviceErrorCount[device._id]=1
            deviceErrorMsg+=`设备-${device.d_nm}(${device.device_num})保存失败;`
            
        }
    }
    return {
        isDeviceError,
        deviceErrorMsg
    }
}

async function batch_upload_avatar(ctx) {
    //filename 文件名称，path上传后文件的临时路径，mimetype文件类型
    const { originalname, filename, mimetype } = ctx.req.file
    const dir = path.join(resConfig.uploader_dir, filename.split(".")[0])
    const params = ctx.req.body
    let user_type = params.user_type || 'register'
    let file_type = params.file_type || 'avatar'
    if (!filename || !user_type
        || [Cons.FEATURE_TYPE_REGISTER, Cons.FEATURE_TYPE_STRANGER].indexOf(user_type) < 0
        || !file_type
        || [Cons.ZIPFILE_TYPE_FEATURE, Cons.ZIPFILE_TYPE_AVATAR].indexOf(file_type) < 0) {
        ctx.error(Code.RET_FEATURE_UPLOAD_ERR_PARAM);
        return
    }
    //返回结果，接下来处理文件
    // ctx.success(Code.RET_FEATURE_UPLOAD_OK);

    //TODO 插入数据库，返回文件路径
    // dataTemp['_id'] = new mongoose.Types.ObjectId();
    let dbId = await downloadModel.create({})
    dbId = dbId._id
    // 0.判断文件目录是否存在
    let logDir = resConfig.downloadlog_dir
    fileUtils.mkdirsSync(logDir)
    // 1.获取文件名称,转换为log路径
    let logName = dbId + '.txt'
    let logPath = path.join(logDir, logName)

    //更新数据库
    await downloadModel.update({ _id: dbId }, {
        file_name: originalname,
        download_time: new Date(),
        log_path: logPath,
        user_type: user_type,
        file_type: file_type
    })
    let deviceErrorHas = {}; //标记设备是否可用
    let asyncAvatarToLibrayQueue = []; //保存照片到底库的任务集合
    // const devices = await ipcModel.find({device_uuid:{$ne:null},register_session:{$ne:null}})
    let result = await new Promise(async (resolve, reject) => {
        const unzipExtractor = unzip.Extract({ path: dir });
        await fs.createReadStream(path.join(resConfig.uploader_dir, filename)).pipe(unzipExtractor)
        //这边担心特征照片过多，所以先解压本地，不缓存内存
        unzipExtractor.on('close', async () => {
            //更新状态，总条数，正常，出错
            let info = {
                total: 0,
                current: 0,
                correctNum: 0,
                errorNum: 0,
                msgs: [],
                deviceMsgs: [],
                msg: '',
                addError(msg) {
                    this.errorNum++
                    this.msgs.push(msg)
                },
                addCorrect() {
                    this.correctNum++
                },
                addOneWithError(msg) {
                    this.current++
                    this.addError(msg)
                },
                addOneWithOutError() {
                    this.current++
                    this.addCorrect()
                },
                checkSum(dbId) {
                    if (this.current != total) {
                        downloadModel.update({ _id: dbId }, {
                            download_statue: Cons.DOWNLOAD_INFO_ERROR
                        })
                    }
                },
                writeTofile(filePath) {
                    let msg = ''
                    msg += '==============基本信息====================\r\n'
                    msg += util.format('共处理数据：\t%s组\r\n', this.total)
                    msg += util.format('上传成功数据：\t%s组\r\n', this.correctNum)
                    msg += util.format('上传失败数据：\t%s组\r\n', this.errorNum)
                    msg += '==============详细信息====================\r\n'
                    for (let index in this.msgs) {
                        msg += util.format('%s\t==>\t%s\r\n', Number(index) + 1, this.msgs[index])
                    }
                    // if(this.deviceMsgs.length){
                    //     msg += '==============底库信息汇总====================\r\n'
                    //     msg += this.deviceMsgs.join('、')+'\r\n'
                    //     msg+='连接异常'
                    // }
                    this.msg = msg;
                    fs.writeFileSync(filePath, msg)
                }
            }
            try {
                let parent = fs.readdirSync(dir)
                info.total = parent.length
                if (file_type == Cons.ZIPFILE_TYPE_FEATURE) {
                    return 
                    /*
                        特征值，未处理
                    */
                    // for (let index in parent) {
                    //     //更新数据库
                    //     await downloadModel.update({ _id: dbId }, {
                    //         download_total: info.total,
                    //         download_current: Number(index) + 1,
                    //         download_statue: Cons.DOWNLOAD_INFO_UPLOADING
                    //     })
                    //     let item = parent[index]
                    //     let statue = fs.statSync(path.join(dir, item))
                    //     if (!statue.isDirectory) {
                    //         info.addOneWithError(util.format('%s 不是文件夹类型，格式错误', item))
                    //         continue
                    //     }
                    //     let regId = await judgeRegId(user_type, item, info)
                    //     if (!regId) {
                    //         continue
                    //     }
                    //     //特征注册
                    //     let imgDatas = []
                    //     let imgNames = []
                    //     try {
                    //         for (let img of fs.readdirSync(path.join(dir, item))) {
                    //             imgNames.push(img)
                    //             imgDatas.push({
                    //                 // picdata: (await ImageUtil.resize(path.join(dir, item, img), 1200)).toString('base64'),
                    //                 picdata: fs.readFileSync(path.join(dir, item, img)).toString('base64'),
                    //                 type: 'jpg',
                    //                 width: '100',
                    //                 height: '100'
                    //             })
                    //             // imgDatas.push(fs.readFileSync(path.join(dir, item, img)).toString('base64'))
                    //         }
                    //     } catch (error) {
                    //         info.addOneWithError(util.format("%s 文件读取错误，注册失败", item))
                    //         continue;
                    //     }
                    //     // 删除注册
                    //     const url = featureUrl.register;
                    //     // 删除特征
                    //     const deleteUrl = featureUrl.delete;

                    //     let result = ""
                    //     try {
                    //         await httpUtil.post(deleteUrl, {
                    //             _id: [regId + ""]
                    //         })
                    //         result = await httpUtil.post(url, {
                    //             _id: regId + "",
                    //             pictures: imgDatas
                    //         })
                    //         if (!result) {
                    //             info.addOneWithError(util.format("%s 服务器错误，注册失败", item))
                    //             continue
                    //         }
                    //     } catch (err) {
                    //         info.addOneWithError(util.format("%s 服务器错误，注册失败", item))
                    //         continue
                    //     }

                    //     let resultMaps = [
                    //         '图像格式有问题', '人脸小于100*100或找不到人脸', '图像中人脸多于一张', '未知错误'
                    //     ]

                    //     if (result.is_success != 0) {
                    //         info.addOneWithError(util.format('%s照片存在问题，注册失败', item))
                    //     } else {
                    //         let flag = ""

                    //         result.pic_results.forEach((res, index) => {
                    //             if (res != 0) {
                    //                 flag += util.format('%s:%s,', imgNames[index], resultMaps[-Number(res) - 1])
                    //             }
                    //         })
                    //         if (flag) {
                    //             info.addOneWithError(util.format('%s一部分照片存在问题，详细信息：【%s】', item, flag))
                    //         } else {
                    //             info.addOneWithOutError()
                    //         }
                    //     }
                    // }
                } else {//头像
                    for (let index in parent) {
                        //更新数据库
                        await downloadModel.update({ _id: dbId }, {
                            download_total: info.total,
                            download_current: Number(index) + 1,
                            download_statue: Cons.DOWNLOAD_INFO_UPLOADING
                        })
                        let item = parent[index]
                        let statue = fs.statSync(path.join(dir, item))
                        if (!statue.isFile()) {
                            info.addOneWithError(util.format('%s 不是文件类型，格式错误', item))
                            continue
                        }
                        let studentName = path.basename(item, path.extname(item))
                        let student = await judgeStuentNum(studentName, info)
                        if (!student) {
                            continue
                        }
                        const studentAvatar = fs.readFileSync(path.join(dir, item)).toString('base64');
                        // //头像注册
                        // let dstfile = regId + '.jpeg';
                        // fs.copyFileSync(path.join(dir, item), path.join(resConfig.avatar_dir, dstfile))
                        await userModel.update({ _id: student._id }, {
                            u_hp: studentAvatar
                        })
                        
                        // 缓存任务
                        asyncAvatarToLibrayQueue.push({
                            student,
                            studentAvatar
                        })
                        // 原来同步的方式改成延迟再执行
                        // const deviceMsg = await save_student_library(student,studentAvatar,isDev,devices,deviceErrorHas)
                        // if(deviceMsg) info.deviceMsgs.push(deviceMsg)
                        info.addOneWithOutError()
                    }
                }
                await downloadModel.update({ _id: dbId }, {
                    download_statue: Cons.DOWNLOAD_INFO_COMPLETED
                })
            } catch (err) {
                info.addOneWithError(util.format("上传出错，当前进度 （%s/%s），详细信息：\r\n", info.current, info.total, err))
            }
            info.writeTofile(logPath)
            // ctx.success({
            //     code: Code.RET_FEATURE_UPLOAD_OK.code,
            //     msg: info.msg
            // });
            resolve(info.msg);
        })

        unzipExtractor.on('error', async (err) => {
            await downloadModel.update({ _id: dbId }, {
                download_statue: Cons.DOWNLOAD_INFO_UNZIPERROR
            })
            throw err
        })
    });
    const isDev = ctx.request.host.indexOf('localhost')>-1
    
    ctx.success({
        code: Code.RET_FEATURE_UPLOAD_OK.code,
        msg: result
    });
    let logtemp
    if(asyncAvatarToLibrayQueue&&asyncAvatarToLibrayQueue.length){
        logtemp = await batchLogModel.create({
            type_name:'批量上传学员头像',
            content:''
        })
    }
    // 执行缓存的任务
    setTimeout(async ()=>{
        const devices = await ipcModel.find({device_uuid:{$ne:null},register_session:{$ne:null}})
        const deviceErrorCount = {}; //标记设备是否可用
        const deviceSuccessCount = {}; //成功数量统计
        devices.forEach(child=>{
            deviceSuccessCount[child.d_nm+'('+child.device_num+')'] = 0
        })
        let logMsg = `共${asyncAvatarToLibrayQueue.length}张照片信息，设备数量共${devices.length}台。`
        await batchLogModel.updateOne({_id:logtemp._id},{content:`${logMsg}正在同步中。。。`})
        let ErrorMsg = ''
        for(let queue of asyncAvatarToLibrayQueue){
            const resSave = await save_student_library(queue.student,queue.studentAvatar,isDev,devices,deviceErrorCount,deviceSuccessCount)
            if(resSave&&resSave.isDeviceError){
                ErrorMsg+=`<div style="text-align:left">${resSave.deviceErrorMsg}</div>`
            }
        }
        let resLog = Object.keys(deviceSuccessCount).map(key=> `${key}:成功${deviceSuccessCount[key]}张`).join(';') + '。'
        logMsg += resLog
        if(ErrorMsg){
            logMsg += `<div style="text-align:center">=====错误信息汇总=====</div>\r\n${ErrorMsg}`
        }
        await batchLogModel.updateOne({_id:logtemp._id},{content:logMsg})
    },1000)
}

async function batch_update_library(ctx){
    const params = ctx.request.body
    let ids = params.ids
    if(!ids||!Array.isArray(ids)){
        return ctx.error(Code.REQUEST_ERROR('请选择学员'))
    }
    ids = [...new Set(ids)]
    const isDev = ctx.request.host.indexOf('localhost')>-1;
    let asyncAvatarToLibrayQueue = []; //保存照片到底库的任务集合
    for(let id of ids){
        const student = await userModel.findById(id)
        if(student){
            asyncAvatarToLibrayQueue.push({
                student,
                studentAvatar:student.u_hp
            })
        }
    }
    if(asyncAvatarToLibrayQueue.length){
        ctx.success(Code.REQUEST_SUCCESS('操作成功，正在后台更新'))
    }else{
        return ctx.error(Code.REQUEST_ERROR('请选择学员'))
    }
    let logtemp
    if(asyncAvatarToLibrayQueue&&asyncAvatarToLibrayQueue.length){
        logtemp = await batchLogModel.create({
            type_name:'批量更新学员到底库',
            content:''
        })
    }
    // 执行缓存的任务
    setTimeout(async ()=>{
        const devices = await ipcModel.find({device_uuid:{$ne:null},register_session:{$ne:null}})
        const deviceErrorCount = {}; //标记设备是否可用
        const deviceSuccessCount = {}; //成功数量统计
        devices.forEach(child=>{
            deviceSuccessCount[child.d_nm+'('+child.device_num+')'] = 0
        })
        let logMsg = `共${asyncAvatarToLibrayQueue.length}张照片信息，设备数量共${devices.length}台。`
        await batchLogModel.updateOne({_id:logtemp._id},{content:`${logMsg}正在同步中。。。`})
        let ErrorMsg = ''
        for(let queue of asyncAvatarToLibrayQueue){
            const resSave = await update_student_library(queue.student,queue.studentAvatar,isDev,devices,deviceErrorCount,deviceSuccessCount)
            if(resSave&&resSave.isDeviceError){
                ErrorMsg+=`<div style="text-align:left">${resSave.deviceErrorMsg}</div>`
            }
        }
        let resLog = Object.keys(deviceSuccessCount).map(key=> `${key}:成功${deviceSuccessCount[key]}张`).join(';') + '。'
        logMsg += resLog
        if(ErrorMsg){
            logMsg += `<div style="text-align:center">=====错误信息汇总=====</div>\r\n${ErrorMsg}`
        }
        await batchLogModel.updateOne({_id:logtemp._id},{content:logMsg})
    },1000)
}

async function empty_student_feature(ctx){
    const req = {
        operate_cmd:5 ,//#1 增加，2 删除 ，3.获取人脸识别结果， 4.获取该图片中人脸特征向量（默认一个最大人脸） 5清空 6同步
        group_id:"-1",// #默认为-1，为待识别人脸所在的库id
        feature_id:-1, //默认为-1， 只有在增加、删除操作有值,对数据库操作时需要确定修改的标识，(该值对应是某个人的id还是某一个人脸特征id需要考虑，并且单独个别修改还是批量修改该如何操作)
    }
    const res = await httpUtil.postWithTimeout(`${globalConfig.faceRecognitionServer}/facerec-ipc`,req)
    if(res.code == 200 && res.result == '200'){
       await userModel.updateMany({u_tp:Cons.USER_TYPE_STUDENT},{feature_id:''})
       await newFaceFeatureModel.remove({})
       return ctx.success(Code.REQUEST_SUCCESS('清空成功'))
    }
    return ctx.error(Code.REQUEST_ERROR('清空失败'))
}

async function sync_student_feature(ctx){
    const req = {
        operate_cmd:6 ,//#1 增加，2 删除 ，3.获取人脸识别结果， 4.获取该图片中人脸特征向量（默认一个最大人脸）5清空 6同步
        group_id:"-1",// #默认为-1，为待识别人脸所在的库id
        feature_id:-1, //默认为-1， 只有在增加、删除操作有值,对数据库操作时需要确定修改的标识，(该值对应是某个人的id还是某一个人脸特征id需要考虑，并且单独个别修改还是批量修改该如何操作)
    }
    const res = await httpUtil.postWithTimeout(`${globalConfig.faceRecognitionServer}/facerec-ipc`,req)
    if(res.code == 200 && res.result == '200'){
       return ctx.success(Code.REQUEST_SUCCESS('同步成功'))
    }
    return ctx.error(Code.REQUEST_ERROR('同步失败'))
}

// 员工批量注册
exports.register_batch = register_batch;
// 访客批量注册
exports.guest_register_batch = guest_register_batch
// 特征批量注册
exports.feature_register_batch = feature_register_batch
// 新增用户
exports.register = register;
// 更新用户信息
exports.update = update;
// 删除用户信息
exports.delete = del;
// 将用户类型还原回陌生人
exports.recovery = recovery;
// 审核访客信息
exports.verify = verify;
// 查询员工信息
exports.query = query;
// 查询员工详情
exports.query_detail = query_detail;
// 查询员工显示头像和特征头像
exports.query_avatar = query_avatar;
// 查询人脸特征
exports.query_feature = query_feature;
// 特征注册
exports.regFeature = regFeature;

// 添加特征
exports.addFeature = addFeature;
// 特征删除
exports.delFeature = delFeature;
// 人脸识别
exports.recognize = recognize;
// 人脸识别
exports.doRecog = doRecog;

// 注册陌生人
exports.reg_stranger = reg_stranger;
// 合并特征
exports.merge_feature = merge_feature;

// 查询员工信息（对APP开放）
exports.query_users = query_users;
// 查询员工头像（对APP开放）
// exports.query_image = query_image;
// 新增访客（对APP开放）
exports.reg_guest = reg_guest;
// 删除访客（对APP开放）
exports.del_guest = del_guest;
// 更新用户头像（对APP开放）
exports.update_avatar = update_avatar;

// 清空用户数据
exports.clean = clean;

// 新增学生
exports.add_student = add_student;
// 编辑
exports.update_student = update_student;
// 删除
exports.delete_student = delete_student;
// 批量上传学生
exports.upload_student = upload_student;
// 批量上传头像
exports.batch_upload_avatar = batch_upload_avatar;
// 批量更新到底库
exports.batch_update_library = batch_update_library;

// 清空学生特征值
exports.empty_student_feature = empty_student_feature
// 同步学生特征值
exports.sync_student_feature = sync_student_feature

// 获取学生信息
exports.get_student_by_id = get_student_by_id;


